package com.gy.boot.common.jwt;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.gy.boot.common.exception.SysException;
import com.gy.boot.common.dao.IEntity;
import com.gy.boot.common.exception.SysException;
import com.gy.boot.common.util.BlankUtil;
import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * JWT: 通过对称加密算法,自制Token验证
 * @author geYang  2020
 */
public class JWT {
    private static Logger logger = LoggerFactory.getLogger(JWT.class);
    /**  JWT过滤后request中存放用户信息的参数名 */
    public static final String TOKEN = "token"; // 校验头凭证
    public static final String USER_ID = "u"; // 用户ID
    public static final String ROLE_ID = "r"; // 角色ID
    private static final String END = "e"; // 结束时间
    private static final long VALID_TIME = 8 * 60 * 60 * 1000; // 有效时间(毫秒)
    private static final byte[] KEY_BYTE = "undifferentiated".getBytes(); // 对称加密秘钥(必须16字节)
    /** 创建加密工具 */
    private static AES getAES() { return SecureUtil.aes(KEY_BYTE); }
    /** 获取过期时间戳 */
    private static long getEnd() { return System.currentTimeMillis() + VALID_TIME; }

    public static final String REFRESH_TOKEN = "REFRESH_TOKEN"; // 刷新Token
    /**
     * 生成Token签名
     * @param userId 用户ID
     * @return 签名字符串
     */
    public static String createToken(long userId) {
        return createToken(userId, null);
    }

    /**
     * 生成Token签名
     * @param userId 用户ID
     * @param roleId 角色ID
     * @return 签名字符串
     * */
    public static String createToken(long userId, Integer roleId) {
        IEntity tokenE = IEntity.create(4);
        tokenE.put(USER_ID, userId);
        if (roleId != null) {
            tokenE.put(ROLE_ID, roleId);
        }
        tokenE.put(END, getEnd());
        return createToken(tokenE);
    }

    /** 创建Token字符串 */
    private static String createToken(IEntity tokenE) {
        // 1.将token数据转为JSON字符串
        String content = JSONUtil.toJsonStr(tokenE);
        // 2.压缩字符串
        byte[] zip = ZipUtil.zlib(content, CharsetUtil.UTF_8, 9);
        // 3.加密为16进制表示的密文
        return getAES().encryptHex(zip);
    }

    /** 解析Token信息 */
    private static IEntity getToken(String token) {
        try {
            // 1.根据密文进行解密数据
            byte[] decrypt = getAES().decrypt(token);
            // 2.进行解压数据
            byte[] unDecrypt = ZipUtil.unZlib(decrypt);
            // 3.转化字符串
            String content = StrUtil.utf8Str(unDecrypt);
            // 4.转化实体
            JSONObject json = JSONUtil.parseObj(content);
            return json.toBean(IEntity.class);
        } catch (Exception e) {
            logger.error("JWT TOKEN 校验失败", e);
            return null;
        }
    }

    /**  校验签名信息 */
    public static IEntity checkToken(String token) {
        if (BlankUtil.isBlank(token)) {
            throw new SysException("请登录后访问", 401);
        }
        IEntity tokenMap = getToken(token);
        if (tokenMap == null) {
            throw new SysException("认证失败, 请登录后访问", 401);
        }
        Long end = tokenMap.getLong(END);
        if (end == null) {
            throw new SysException("认证失败, 请登录后访问", 401);
        }
        long now = System.currentTimeMillis();
        if (now > end) {
            throw new SysException("登录已过期, 请重新登录", 401);
        }
        // 剩余时间 < 30%有效时间; 刷新SQL
        boolean isRefresh = (end - now) < (long) (VALID_TIME * 0.3);
        if (isRefresh) {
            String refreshToken = createToken(tokenMap.getLong(USER_ID), tokenMap.getInt(ROLE_ID));
            tokenMap.put(REFRESH_TOKEN, refreshToken);
        }
        return tokenMap;
    }



    /**
     * 设置Token到客户端
     * */
    public static void setToken(HttpServletRequest request, HttpServletResponse response, String token) {
        response.addHeader(JWT.TOKEN, token);
        request.getSession().setAttribute(JWT.TOKEN, token);
        // 设置 cookie
        Cookie cookie = new Cookie(JWT.TOKEN, token);
        cookie.setPath("/"); // 必须将 path 设置为 /, 否则其他页面无法获取
        cookie.setHttpOnly(true); // 不许通过脚本访问cookie
        response.addCookie(cookie);
    }


}
